<!-- Copyright 2013 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -->
<!DOCTYPE html>
<title>Unit Test of e2e.block.Factory</title>
<script src="../../../../../../javascript/closure/base.js"></script>
<script src="test_js_deps-runfiles.js"></script>
<script>
  goog.require('goog.array');
  goog.require('goog.crypt');
  goog.require('goog.testing.AsyncTestCase');
  goog.require('goog.testing.jsunit');
  goog.require('e2e.cipher.Aes');
  goog.require('e2e.cipher.Algorithm');
  goog.require('e2e.cipher.Error');
  goog.require('e2e.cipher.Rsa');
  goog.require('e2e.cipher.factory');
  goog.require('e2e.compression.all');
  goog.require('e2e.hash.Sha1');
  goog.require('e2e.openpgp.EncryptedCipher');
  goog.require('e2e.openpgp.S2k');
  goog.require('e2e.openpgp.asciiArmor');
  goog.require('e2e.openpgp.block.all');
  goog.require('e2e.openpgp.block.factory');
  goog.require('e2e.openpgp.error.SignatureError');
  goog.require('e2e.openpgp.packet.Signature');
  goog.require('e2e.openpgp.packet.UserId');
</script>
<textarea id="SmallRSAPublicKeyPacket" style="display:none">
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v1
Comment: Use "gpg --dearmor" for unpacking

mG0EU/o5NgEDAMOG0nCsTGXWd8S9lvY6804VtlA9BD+7HYmc56lyNA4P5lBHD/B0
BQIJtq9nadZ9ggnYQhy26DRMGuDpIQaZ0HhtgsXfrDIQdiaWBU1Dhuuj6MNyq54E
gLeOSuwQb+cT0wARAQAB
=MZ6s
-----END PGP ARMORED FILE-----
</textarea>
<textarea id="UserIDPacket" style="display:none">
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v1

tBBzbWFsbC1yc2EtbWFzdGVy
=x6IC
-----END PGP ARMORED FILE-----
</textarea>
<textarea id="RSASignaturePacket" style="display:none">
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v1

iKgEEwECABIFAlR+AT4CGwMCCwcCFQgCHgEACgkQ/LnCIvzh8C572AP7BKLEK9ZD
xP3V9fPgwzSczNueS0u7knKpTE51Y8Xh1s/+GkLtTUoep5JOpJ/17hjVWRrxni27
jJ1mSHjzNeUvO+EcNOgvqKXisPyuvWY9PJUGjVy84SoSI6xG0GOgg6fOO6Wh/jzd
vUSHEmCH1hvXIGVkjXJDoTeUFX0xmYRE30E=
=vHP8
-----END PGP ARMORED FILE-----
</textarea>
<textarea id="ElGamalSignaturePacket" style="display:none">
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v1

iQE/AwUQN4qCbaNldhxA9FnnEAOMdwSfc28FsuhUjqbZ+Qh+qT8MBY1qujdh18LF
juG2nnuCDBADwQDXxEy69LKLI1ndig1FQDyo/5K8DP3wWFnsC6YctacCsVM8Z/kF
3BqeKAxk6wZt50jlj5XYqmuLdEaH9xKziZyXzU3z6Y31fkqXSwVlzHbWJ6oQfHLk
PgagJqkuaK5OLVFupZEKFMiQ3bmtyzXe/jYVZgSfeTaJ8CIGwEIGnkjHEH9SU43Z
L+T11CRxLozWAnU4UTlfyr7JwK1MAy+AbW+755rHtsIb8eOA9RTZCT7ip5vazg4K
UDIvQCXdP2j7smoRtKegutgRUMnny5CdRLEK5VNGZBnI3bKPdASpmay8u144hZnb
s5ZFSR4rrNTnWoaW1ECJkVoQwoTXvFNCUNJ2aKjy8iD8TQ==
=b/1f
-----END PGP ARMORED FILE-----
</textarea>
<textarea id="SmallRSAKeyBlock" style="display:none">
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mG0EU/o5NgEDAMOG0nCsTGXWd8S9lvY6804VtlA9BD+7HYmc56lyNA4P5lBHD/B0
BQIJtq9nadZ9ggnYQhy26DRMGuDpIQaZ0HhtgsXfrDIQdiaWBU1Dhuuj6MNyq54E
gLeOSuwQb+cT0wARAQABtBBzbWFsbC1yc2EtbWFzdGVyiIgEEwECABIFAlR+DzcC
GwMCCwcCFQgCHgEACgkQHjK/7rQW+j2BHgMAutNx2bO2ZokFz0zHptkVzdGbu7aJ
gHRv6Kyhu4nlybXC/nLdTf1M2lwq/NJGOuGa+5/PjkTFqExEALBV8oMjKYGopHk6
aqpRTIYk0ovAicOF3bIM/D3uy6s+uQugCBMu
=s/5y
-----END PGP PUBLIC KEY BLOCK-----
</textarea>
<textarea id="OkRSAKeyBlock" style="display:none">
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mI0EU/o8SwEEALkvOE64xctaf8YlRDlxpoyMxEoVE0iBT9o7wYPRyth/hqGKD8rT
F2t/FCF3bmJnxdZvitElcp7UxQyVgeMwlhMIJSj9CtHwHRiZ6CY9gp8Swh1srlsU
VoZqGJGHgt4GKM9YPL6NCpFHdZhqp1COWUpeBrefvEtJTbNDjJuG+uw9ABEBAAG0
CnJzYS1tYXN0ZXKIqAQTAQIAEgUCVH4SSwIbAwILBwIVCAIeAQAKCRC3IbqhsdLn
K8FpA/0R/aQx8ebgQ3E0ouia06AyHRTY5SNnWRELzAP3f1H+aFVrI11iBjPvc3pK
Jbnh68mtlDNWIi1pJBrWHNwOBg0Spj8Jbt8iK8Tr/E8Wn09C1vn+xJYNMUh4tjEl
z2DimuS4AefmevwVtuJH1FcSgdxcw9iGbsapeGC9JuVC0VK58w==
=/7L/
-----END PGP PUBLIC KEY BLOCK-----
</textarea>
<textarea id="WeakSignatureKeyBlock" style="display:none">
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

mQCNA1SA9Z8AAAEEAKIXRwS5KCk6H7gi9DGAOlEiNkIxnoC6BxmIYiv6Mr3PRScj
/L0w7Q/uocZVKLUp6BK11FYWA9t5QtLW4Jr1o2nHFvaU/GpQJSy0mAIrg3wlW3Aq
GHKZfzUVjkAeOuYYy7lOCpIKTmQnM5uiiso/v5rQVTTnh2E+Qr6NycLHqCRhAAUR
tAthYmNAZGVmLmNvbYkAlQMFEFSA9Z++jcnCx6gkYQEBMsED/3EGVTBWO/jq1x3C
CyBm//Yrg98qe86NMwdmIhPx9Pnsa5TCPsXea132W4eoWLDKlzhJe65PX2/W1DIk
/za6YMeNzRkkbyvPKnZg6plKQDvfaX6LqyX752+DAFZ76EGeJI4wNWR/wh1S49Hr
7PCfaYY10Sfes4D8KMcNN9nReNbU
=3umo
-----END PGP PUBLIC KEY BLOCK-----
</textarea>

<script>

var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(document.title);

function getBytes(id) {
  return e2e.openpgp.asciiArmor.parse(
      document.getElementById(id).value).data;
}


function testParsePublicKey() {
  var packets = [
    new e2e.openpgp.packet.PublicKey(4, 0,
        e2e.cipher.factory.get(e2e.cipher.Algorithm.RSA)),
    new e2e.openpgp.packet.UserId('first userid')
  ];
  var block = e2e.openpgp.block.factory.parseTransferableKey(packets);
  assertTrue(block instanceof e2e.openpgp.block.TransferablePublicKey);
  assertEquals(0, packets.length);
}

// Test for bug regression.
function testPublicKeyExtraPackets() {
  var packets = [
    new e2e.openpgp.packet.PublicKey(4, 0,
        e2e.cipher.factory.get(e2e.cipher.Algorithm.RSA)),
    new e2e.openpgp.packet.UserId('first userid'),
    new e2e.openpgp.packet.PublicKey(4, 0,
        e2e.cipher.factory.get(e2e.cipher.Algorithm.RSA)),
    new e2e.openpgp.packet.UserId('second userid')
  ];
  var block = e2e.openpgp.block.factory.parseTransferableKey(packets);
  assertTrue(block instanceof e2e.openpgp.block.TransferablePublicKey);
  assertEquals(2, packets.length);
}

function testParseSecretKey() {
  var fakeCipher = new e2e.cipher.Aes(
    e2e.cipher.Algorithm.AES128,
    {key: goog.array.repeat(0x16, 16)});
  var encryptedFakeCipher = new e2e.openpgp.EncryptedCipher(
    goog.array.repeat(0xDD, 32),
    e2e.openpgp.EncryptedCipher.KeyDerivationType.S2K_SHA1,
    fakeCipher,
    e2e.cipher.Algorithm.AES128,
    goog.array.repeat(0xFF, 16),
    new e2e.openpgp.SimpleS2K(new e2e.hash.Sha1));
  var packets = [
    new e2e.openpgp.packet.SecretKey(4, 0, encryptedFakeCipher),
    new e2e.openpgp.packet.UserId('first userid'),
    new e2e.openpgp.packet.PublicKey(4, 0,
        e2e.cipher.factory.get(e2e.cipher.Algorithm.RSA))
  ];
  var block = e2e.openpgp.block.factory.parseTransferableKey(packets);
  assertTrue(block instanceof e2e.openpgp.block.TransferableSecretKey);
  assertEquals(1, packets.length);
}

function testParseEncryptedMessage() {
  // Construct a message with an invalid trailer.
  var packets = [
    new e2e.openpgp.packet.SymmetricKey(4, e2e.cipher.Algorithm.AES256,
        [], new e2e.openpgp.SimpleS2K(new e2e.hash.Sha1)),
    new e2e.openpgp.packet.EncryptedData([]),
    new e2e.openpgp.packet.UserId('unused packet')
  ];
  // Test that we detect this.
  var mustThrow = assertThrows(function() {
    e2e.openpgp.block.factory.parseMessage(packets);
  });
  assertEquals(
      'Unexpected packets following message block.', mustThrow.message);
  // Regardless, ensure we only consumed two packets
  assertEquals(1, packets.length);

  // Remove trailer, and try again.
  packets = [
    new e2e.openpgp.packet.SymmetricKey(4, e2e.cipher.Algorithm.AES256,
        [], new e2e.openpgp.SimpleS2K(new e2e.hash.Sha1)),
    new e2e.openpgp.packet.EncryptedData([])
  ];

  var block = e2e.openpgp.block.factory.parseMessage(packets);
  assertTrue(block instanceof e2e.openpgp.block.EncryptedMessage);
  // We should have consumed everything as well.
  assertEquals(0, packets.length);
}

function testParseLiteralMessage() {
  var packets = [
    new e2e.openpgp.packet.LiteralData(
        e2e.openpgp.packet.LiteralData.Format.UTF8, [], 0, []),
    new e2e.openpgp.packet.UserId('unused packet')
  ];
  // Message blocks with trailing content should be rejected.
  var mustThrow = assertThrows(function() {
    e2e.openpgp.block.factory.parseMessage(packets);
  });
  assertEquals(
      'Unexpected packets following message block.', mustThrow.message);
  // Regardless, confirm we only consumed one packet.
  assertEquals(1, packets.length);

  // Try again, without the trailing packet.
  packets = [
    new e2e.openpgp.packet.LiteralData(
        e2e.openpgp.packet.LiteralData.Format.UTF8, [], 0, [])
  ];
  var block = e2e.openpgp.block.factory.parseMessage(packets);
  assertTrue(block instanceof e2e.openpgp.block.LiteralMessage);
  // And we should have consumed everything.
  assertEquals(0, packets.length);
}


function testParseCompressedMessage() {
  var msg = e2e.stringToByteArray('test');
  var literal = new e2e.openpgp.packet.LiteralData(
                  e2e.openpgp.packet.LiteralData.Format.UTF8,
                  [], 0, msg);
  var packets = [
    e2e.openpgp.packet.Compressed.construct(literal.serialize())
  ];

  var block = e2e.openpgp.block.factory.parseMessage(packets);
  assertTrue(block instanceof e2e.openpgp.block.Compressed);
  assertArrayEquals(block.getMessage().getLiteralMessage().getData(), msg);

  // compressed packets with a nested invalid message block should be
  // rejected when its content is requested. This compressed packet
  // contains two literal data packets, which is an invalid message
  // block.
  packets = [
    e2e.openpgp.packet.Compressed.construct(
        goog.array.concat(literal.serialize(), literal.serialize()))
  ];
  // the packet sequence should parse
  block = e2e.openpgp.block.factory.parseMessage(packets);
  assertTrue(block instanceof e2e.openpgp.block.Compressed);
  // but obtaining the nested message block should fail.
  var mustThrow = assertThrows(function() {
    block.getMessage();
  });
  assertEquals(
      'Unexpected packets following message block.', mustThrow.message);
}


function testParsingLeavesByteArrayIntact() {
  // Construct packet sequence with invalid message block.
  var packets = [
    new e2e.openpgp.packet.LiteralData(
        e2e.openpgp.packet.LiteralData.Format.UTF8, [], 0, []),
    new e2e.openpgp.packet.UserId('unused packet')
  ];
  var mustThrow = assertThrows(function() {
    e2e.openpgp.block.factory.parseMessage(packets);
  });
  assertEquals(
      'Unexpected packets following message block.', mustThrow.message);
  // Regardless, only one packet should be consumed.
  assertEquals(1, packets.length);

  // New sequence with valid message block.
  var packets = [
    new e2e.openpgp.packet.LiteralData(
        e2e.openpgp.packet.LiteralData.Format.UTF8, [], 0, [])
  ];
  var block = e2e.openpgp.block.factory.parseMessage(packets);
  assertTrue(block instanceof e2e.openpgp.block.LiteralMessage);
  assertArrayEquals([], block.getData());
  // All packets must be consumed.
  assertEquals(0, packets.length);

  // serialize parsed message into bytearray, parse back bytearray,
  // check that we didn't disturb the bytearray, and that we got back
  // the same message.
  var serialization = block.serialize();
  var serializationClone = goog.array.clone(serialization);
  var reparsedBlock = e2e.openpgp.block.factory.parseByteArrayMessage(
      serialization);
  // bytearray should stay undisturbed.
  assertArrayEquals(serialization, serializationClone);
  // message block should be the same as before.
  assertTrue(reparsedBlock instanceof e2e.openpgp.block.LiteralMessage);
  assertArrayEquals(reparsedBlock.getData(), block.getData());
}


// Tests that unsupported or bad packets are skipped
// on request, while the good ones are retained.
function testSkippedPackets() {
  var packetBytes =
      goog.array.concat(
          getBytes('SmallRSAPublicKeyPacket'),
          getBytes('UserIDPacket'),
          getBytes('RSASignaturePacket'),
          getBytes('ElGamalSignaturePacket'),
          getBytes('RSASignaturePacket'));

  // Normally, this should just fail on the first packet.
  var mustThrow = assertThrows(function() {
    e2e.openpgp.block.factory.byteArrayToPackets(packetBytes);
  });
  assertTrue(mustThrow instanceof e2e.cipher.Error);

  // We should skip the first and fourth packet, and return the
  // remaining packets.
  var packets =
      e2e.openpgp.block.factory.byteArrayToPackets(packetBytes, true);
  assertEquals(3, packets.length);
  assertTrue(packets[0] instanceof e2e.openpgp.packet.UserId);
  assertTrue(packets[1] instanceof e2e.openpgp.packet.Signature);
  assertTrue(packets[2] instanceof e2e.openpgp.packet.Signature);
}

// Tests that keyrings can be partially loaded on request, even if
// they contain unsupported keys.

function testPartialKeyringImport_unsupportedKeys() {
  var bytes =
      goog.array.concat(
          getBytes('SmallRSAKeyBlock'),
          getBytes('OkRSAKeyBlock'),
          getBytes('RSASignaturePacket'));
  var keys =
      e2e.openpgp.block.factory.parseByteArrayAllTransferableKeys(bytes, true);
  assertEquals(1, keys.length);
  asyncTestCase.waitForAsync('Waiting for signature processing.');
  keys[0].processSignatures().then(function() {
    assertEquals(1, keys[0].userIds.length);
    assertEquals("rsa-master", keys[0].userIds[0].userId);

    // Should be the same if we reverse the sequence of keys.
    bytes =
        goog.array.concat(
            getBytes('OkRSAKeyBlock'),
            getBytes('RSASignaturePacket'),
            getBytes('SmallRSAKeyBlock'));
    keys =
        e2e.openpgp.block.factory.parseByteArrayAllTransferableKeys(bytes, true);
    assertEquals(1, keys.length);
    return keys[0].processSignatures();
  }).then(function() {
    assertEquals(1, keys[0].userIds.length);
    assertEquals("rsa-master", keys[0].userIds[0].userId);
    asyncTestCase.continueTesting();
  });
}

// Tests that keyrings can be partially loaded on request, even if
// they contain weak signatures.

function testPartialKeyringImport_weakSignatures() {
  var bytes =
      goog.array.concat(
          getBytes('WeakSignatureKeyBlock'),
          getBytes('OkRSAKeyBlock'));
  // This step simply parses keys, and does not check signatures. So,
  // we expect it to work even with the stricter parsing rules.
  var keys =
      e2e.openpgp.block.factory.parseByteArrayAllTransferableKeys(bytes);
  assertEquals(2, keys.length);
  assertEquals(1, keys[0].unverifiedUserIds_.length);
  assertEquals("abc@def.com", keys[0].unverifiedUserIds_[0].userId);
  assertEquals(1, keys[1].unverifiedUserIds_.length);
  assertEquals("rsa-master", keys[1].unverifiedUserIds_[0].userId);
  asyncTestCase.waitForAsync('Waiting for signature processing to fail.');
  keys[0].processSignatures().then(function() {
    fail('processSignatures must fail');
  }, function(error) {
    assertTrue(error instanceof e2e.openpgp.error.SignatureError);
    asyncTestCase.waitForAsync('Waiting for key extraction to fail.');
    return e2e.openpgp.block.factory.extractKeys(keys);
  }).then(function() {
    fail('extractKeys must fail');
  }, function(error) {
    assertTrue(error instanceof e2e.openpgp.error.SignatureError);

    asyncTestCase.waitForAsync('Waiting for key extraction to succeed.');
    // However, we expect to see the key skipped when extracting keys
    // with opt_skiponerror turned on.
    return e2e.openpgp.block.factory.extractKeys(keys, true);
  }).then(function(extracted) {
    assertEquals(1, extracted.length);
    assertEquals(1, extracted[0].uids.length);
    assertEquals("rsa-master", extracted[0].uids[0]);
    asyncTestCase.continueTesting();
  }, fail)
}


</script>
